package cn.usbtg.sutil.filescanner;

import cn.usbtg.sutil.EmptyUtil;

import java.util.ArrayList;
import java.util.Enumeration;
import java.util.List;
import java.util.jar.JarEntry;
import java.util.jar.JarFile;

/**
 * 文件扫描器
 */
public class FileScanner {
    //类加载路径
    private static final String CLASS_PATH = FileScanner.class.getResource("/").getPath();

    /**
     * (vip)查找指定baseDirPath路径下与matchesStr匹配的目录
     *
     * @param baseDirPath 起始文件夹路径
     * @param matchesStr  文件夹名称(不包含路径)匹配规则(正则表达式)
     * @return List<java.io.File>
     */
    public static List<java.io.File> findDirs(String baseDirPath, String matchesStr) {
        try {
            List<java.io.File> dirs = new ArrayList<java.io.File>();

            java.io.File fileBaseDir = new java.io.File(baseDirPath);
            if (!fileBaseDir.exists()) {
                System.err.println("directory not exists:" + baseDirPath);
                return null;
            }

            java.io.File[] filelist = fileBaseDir.listFiles();
            if (filelist == null) {
                return null;
            }

            for (java.io.File file : filelist) {
                if (file.isDirectory()) {
                    if (file.getName().matches(matchesStr)) {
                        dirs.add(file.getAbsoluteFile());
                    }
                    dirs.addAll(findDirs(file.getPath(), matchesStr));
                }
            }

            return dirs;
        } catch (Exception e) {
            System.err.println(e.getClass().getName() + ":" + e.getMessage());

        } catch (Error e) {
            System.err.println(e.getClass().getName() + ":" + e.getMessage());
        }

        return null;
    }

    /**
     * 查找指定baseDirPath路径下的目录
     *
     * @param matchesStr 文件夹名称(不包含路径)匹配规则(正则表达式)
     * @return List<java.io.File>
     */
    public static List<java.io.File> findDirs(String matchesStr) {
        return findDirs(CLASS_PATH, matchesStr);
    }

    /**
     * 查找类加载路径下的目录
     *
     * @return List<java.io.File>
     */
    public static List<java.io.File> findDirs() {
        return findDirs(CLASS_PATH, ".*");
    }

    /**
     * (vip)查找指定baseDirPath路径下与matchesStr匹配的文件
     *
     * @param baseDirPath  起始文件夹路径
     * @param matchesStr   文件名称(不包含路径)匹配规则(正则表达式)
     * @param scanChildDir 是否扫描子文件夹
     * @return List<java.io.File>
     */
    public static List<java.io.File> findFiles(String baseDirPath, String matchesStr, boolean scanChildDir) {
        try {
            List<java.io.File> files = new ArrayList<java.io.File>();

            java.io.File fileBaseDir = new java.io.File(baseDirPath);
            if (!fileBaseDir.exists()) {
                System.err.println("directory not exists:" + baseDirPath);
                return null;
            }
            if (!fileBaseDir.isDirectory()) {
                System.err.println("not a directory:" + baseDirPath);
                return null;
            }

            java.io.File[] filelist = fileBaseDir.listFiles();
            if (filelist == null) {
                return null;
            }

            for (java.io.File file : filelist) {
                if (file.isDirectory()) {
                    if (scanChildDir) {
                        files.addAll(findFiles(file.getPath(), matchesStr, scanChildDir));
                    }
                    continue;
                }

                if (file.getName().matches(matchesStr)) {
                    files.add(file.getAbsoluteFile());
                }
            }

            return files;
        } catch (Exception e) {
            System.err.println(e.getClass().getName() + ":" + e.getMessage());
        } catch (Error e) {
            System.err.println(e.getClass().getName() + ":" + e.getMessage());
        }

        return null;
    }

    /**
     * 查找指定baseDirPath路径下(包含子文件夹)与matchesStr匹配的文件
     *
     * @param baseDirPath 起始文件夹路径
     * @param matchesStr  文件名称(不包含路径)匹配规则(正则表达式)
     * @return List<java.io.File>
     */
    public static List<java.io.File> findFiles(String baseDirPath, String matchesStr) {
        return findFiles(baseDirPath, matchesStr, true);
    }

    /**
     * 查找类加载路径下(包含子文件夹)与matchesStr匹配的文件
     *
     * @param matchesStr 文件名称(不包含路径)匹配规则(正则表达式)
     * @return List<java.io.File>
     */
    public static List<java.io.File> findFiles(String matchesStr) {
        return findFiles(CLASS_PATH, matchesStr, true);
    }

    /**
     * 查找类加载路径下(包含子文件夹)所有文件
     *
     * @return List<java.io.File>
     */
    public static List<java.io.File> findFiles() {
        return findFiles(CLASS_PATH, ".*", true);
    }

    /**
     * (vip)查找指定jar文件中与matchesStr4Package匹配的包下与matchesStr4File匹配的文件
     *
     * @param fileJar            jar文件
     * @param matchesStr4Package 包名匹配规则(正则表达式)
     * @param matchesStr4File    文件名称(不包含包名)匹配规则(正则表达式)
     * @return List<cn.usbtg.sutil.filescanner.File>
     */
    public static List<cn.usbtg.sutil.filescanner.File> findFilesFromJar(java.io.File fileJar, String matchesStr4Package, String matchesStr4File) {
        try {
            //包名未设置匹配时，默认匹配所有包
            matchesStr4Package = EmptyUtil.isNotEmpty(matchesStr4Package) ? matchesStr4Package : ".*";

            List<cn.usbtg.sutil.filescanner.File> files = new ArrayList<cn.usbtg.sutil.filescanner.File>();
            JarFile jarFile = new JarFile(fileJar.getAbsolutePath());
            Enumeration<JarEntry> entrys = jarFile.entries();
            while (entrys.hasMoreElements()) {
                JarEntry jarEntry = entrys.nextElement();

                String entryName = jarEntry.getName();

                //如果为package，跳过
                if (entryName.substring(entryName.length() - 1).equals("/")) {
                    continue;
                }

                String packageName = entryName.contains("/") ? entryName.substring(0, entryName.lastIndexOf("/") + 1).replace("/", ".") : "";
                String fileName = entryName.contains("/") ? entryName.substring(entryName.lastIndexOf("/") + 1) : entryName;
                if (packageName.matches(matchesStr4Package) && fileName.matches(matchesStr4File)) {
                    files.add(new cn.usbtg.sutil.filescanner.File(packageName, fileName));
                }
            }
            return files;
        } catch (Exception e) {
            System.err.println(e.getClass().getName() + ":" + e.getMessage());
        } catch (Error e) {
            System.err.println(e.getClass().getName() + ":" + e.getMessage());
        }

        return null;
    }

    /**
     * 查找指定jar文件(包、子包)中与matchesStr4File匹配的文件
     *
     * @param fileJar         jar文件
     * @param matchesStr4File 文件名称(不包含包名)匹配规则(正则表达式)
     * @return List<cn.usbtg.sutil.filescanner.File>
     */
    public static List<cn.usbtg.sutil.filescanner.File> findFilesFromJar(java.io.File fileJar, String matchesStr4File) {
        return findFilesFromJar(fileJar, ".*", matchesStr4File);
    }

    /**
     * 查找指定jar文件(包、子包)中的文件
     *
     * @param fileJar jar文件
     * @return List<cn.usbtg.sutil.filescanner.File>
     */
    public static List<cn.usbtg.sutil.filescanner.File> findFilesFromJar(java.io.File fileJar) {
        return findFilesFromJar(fileJar, ".*", ".*");
    }

    /**
     * (vip)查找指定jar文件列表中与matchesStr4Package匹配的包下与matchesStr4File匹配的文件
     *
     * @param filesJar           jar文件列表
     * @param matchesStr4Package 包名匹配规则(正则表达式)
     * @param matchesStr4File    文件名称(不包含包名)匹配规则(正则表达式)
     * @return List<cn.usbtg.sutil.filescanner.File>
     */
    public static List<cn.usbtg.sutil.filescanner.File> findFilesFromJars(List<java.io.File> filesJar, String matchesStr4Package, String matchesStr4File) {
        try {
            List<cn.usbtg.sutil.filescanner.File> files = new ArrayList<cn.usbtg.sutil.filescanner.File>();
            for (java.io.File fileJar : filesJar) {
                files.addAll(findFilesFromJar(fileJar, matchesStr4Package, matchesStr4File));
            }
            return files;
        } catch (Exception e) {
            System.err.println(e.getClass().getName() + ":" + e.getMessage());
        } catch (Error e) {
            System.err.println(e.getClass().getName() + ":" + e.getMessage());
        }

        return null;
    }

    /**
     * 查找指定jar文件列表(包、子包)中与matchesStr4File匹配的文件
     *
     * @param filesJar        jar文件列表
     * @param matchesStr4File 文件名称(不包含包名)匹配规则(正则表达式)
     * @return List<cn.usbtg.sutil.filescanner.File>
     */
    public static List<cn.usbtg.sutil.filescanner.File> findFilesFromJars(List<java.io.File> filesJar, String matchesStr4File) {
        return findFilesFromJars(filesJar, ".*", matchesStr4File);
    }

    /**
     * 查找指定jar文件列表(包、子包)中的文件
     *
     * @param filesJar jar文件列表
     * @return List<cn.usbtg.sutil.filescanner.File>
     */
    public static List<cn.usbtg.sutil.filescanner.File> findFilesFromJars(List<java.io.File> filesJar) {
        return findFilesFromJars(filesJar, ".*", ".*");
    }

    /**
     * (vip)查找指定文件夹下的class
     *
     * @param baseDirPath             起始文件夹路径
     * @param scanChildDir            是否扫描子文件夹
     * @param pre                     class文件File对象获取类名的前缀
     * @param matchesStr4Package      包名匹配规则(正则表达式)
     * @param matchesStr4File         文件名称(不包含路径)匹配规则(正则表达式)
     * @param parentClass             父类class
     * @param parentClassIsInstanceOf 父类class是否多重继承
     * @return List<Class>
     */
    public static List<Class> findClass(String baseDirPath, boolean scanChildDir, String pre, String matchesStr4Package, String matchesStr4File, Class parentClass, boolean parentClassIsInstanceOf) {
        try {
            List<Class> classes = new ArrayList<Class>();

            //获取baseDirPath下的class文件
            List<java.io.File> filesClasses = findFiles(baseDirPath, ".*\\.class", scanChildDir);
            for (java.io.File file : filesClasses) {
                String fileName = file.getName();

                //若文件名匹配
                //1、制定了父类时，若父类匹配，添加到结果
                //2、未指定父类时，直接添加到结果
                if (fileName.matches(matchesStr4File)) {
                    String fileClassName = file2ClassName(file, pre);
                    Class fileClass = null;
                    try {
                        fileClass = Class.forName(fileClassName);
                    } catch (Exception e) {
                        System.err.println(e.getClass().getName() + "[" + fileClassName + "]:" + e.getMessage());
                    } catch (Error e) {
                        System.err.println(e.getClass().getName() + "[" + fileClassName + "]:" + e.getMessage());
                    }

                    //未找到class时跳过
                    if (EmptyUtil.isEmpty(fileClass)) {
                        continue;
                    }

                    //class未annotation或interface或enum时跳过
                    if (fileClass.isAnnotation() || fileClass.isInterface() || fileClass.isEnum()) {
                        continue;
                    }

                    //package不匹配时，跳过
                    if (!fileClass.getPackage().getName().matches(matchesStr4Package)) {
                        continue;
                    }

                    if (EmptyUtil.isNotEmpty(parentClass)) {
                        if (parentClassIsInstanceOf) {
                            if (parentClass.isAssignableFrom(fileClass)) {
                                classes.add(fileClass);
                            }
                        } else {
                            if (parentClass.equals(fileClass.getSuperclass())) {
                                classes.add(fileClass);
                            }
                        }
                    } else {
                        classes.add(fileClass);
                    }
                }
            }

            return classes;
        } catch (Exception e) {
            e.printStackTrace();
            System.err.println(e.getClass().getName() + ":" + e.getMessage());
        } catch (Error e) {
            e.printStackTrace();
            System.err.println(e.getClass().getName() + ":" + e.getMessage());
        }

        return null;
    }

    /**
     * (vip)查找指定文件夹下的class
     *
     * @param baseDirPath             起始文件夹路径
     * @param scanChildDir            是否扫描子文件夹
     * @param pre                     class文件File对象获取类名的前缀
     * @param matchesStr4File         文件名称(不包含路径)匹配规则(正则表达式)
     * @param parentClass             父类class
     * @param parentClassIsInstanceOf 父类class是否多重继承
     * @return List<Class>
     */
    public static List<Class> findClass(String baseDirPath, boolean scanChildDir, String pre, String matchesStr4File, Class parentClass, boolean parentClassIsInstanceOf) {
        return findClass(baseDirPath, scanChildDir, pre, ".*", matchesStr4File, parentClass, parentClassIsInstanceOf);
    }

    /**
     * 查找指定文件夹下的class
     *
     * @param baseDirPath     起始文件夹路径
     * @param scanChildDir    是否扫描子文件夹
     * @param pre             class文件File对象获取类名的前缀
     * @param matchesStr4File 文件名称(不包含路径)匹配规则(正则表达式)
     * @param parentClass     父类class
     * @return List<Class>
     */
    public static List<Class> findClass(String baseDirPath, boolean scanChildDir, String pre, String matchesStr4File, Class parentClass) {
        return findClass(baseDirPath, scanChildDir, pre, ".*", matchesStr4File, parentClass, true);
    }

    /**
     * 查找指定文件夹下的class
     *
     * @param baseDirPath     起始文件夹路径
     * @param scanChildDir    是否扫描子文件夹
     * @param pre             class文件File对象获取类名的前缀
     * @param matchesStr4File 文件名称(不包含路径)匹配规则(正则表达式)
     * @return List<Class>
     */
    public static List<Class> findClass(String baseDirPath, boolean scanChildDir, String pre, String matchesStr4File) {
        return findClass(baseDirPath, scanChildDir, pre, ".*", matchesStr4File, null, true);
    }

    /**
     * 查找指定文件夹下的class
     *
     * @param baseDirPath     起始文件夹路径
     * @param pre             class文件File对象获取类名的前缀
     * @param matchesStr4File 文件名称(不包含路径)匹配规则(正则表达式)
     * @param parentClass     父类class
     * @return List<Class>
     */
    public static List<Class> findClass(String baseDirPath, String pre, String matchesStr4File, Class parentClass) {
        return findClass(baseDirPath, true, pre, ".*", matchesStr4File, parentClass, true);
    }

    /**
     * 查找指定文件夹下的class
     *
     * @param baseDirPath     起始文件夹路径
     * @param pre             class文件File对象获取类名的前缀
     * @param matchesStr4File 文件名称(不包含路径)匹配规则(正则表达式)
     * @return List<Class>
     */
    public static List<Class> findClass(String baseDirPath, String pre, String matchesStr4File) {
        return findClass(baseDirPath, true, pre, ".*", matchesStr4File, null, true);
    }

    /**
     * 查找指定文件夹下的class
     *
     * @param baseDirPath 起始文件夹路径
     * @param pre         class文件File对象获取类名的前缀
     * @param parentClass 父类class
     * @return List<Class>
     */
    public static List<Class> findClass(String baseDirPath, String pre, Class parentClass) {
        return findClass(baseDirPath, true, pre, ".*", ".*", parentClass, true);
    }

    /**
     * 查找指定文件夹下的class
     *
     * @param baseDirPath 起始文件夹路径
     * @param pre         class文件File对象获取类名的前缀
     * @return List<Class>
     */
    public static List<Class> findClass(String baseDirPath, String pre) {
        return findClass(baseDirPath, true, pre, ".*", ".*", null, true);
    }

    /**
     * 查找指定文件夹下的class
     *
     * @param pre         class文件File对象获取类名的前缀
     * @param parentClass 父类class
     * @return List<Class>
     */
    public static List<Class> findClass(String pre, Class parentClass) {
        return findClass(CLASS_PATH, true, pre, ".*", ".*", parentClass, true);
    }

    /**
     * 查找指定文件夹下的class
     *
     * @param pre class文件File对象获取类名的前缀
     * @return List<Class>
     */
    public static List<Class> findClass(String pre) {
        return findClass(CLASS_PATH, true, pre, ".*", ".*", null, true);
    }

    /**
     * (vip)查找指定jar文件中的class
     *
     * @param fileJar                 jar文件File对象
     * @param matchesStr4Package      包名匹配规则(正则表达式)(只与jar文件有关)
     * @param matchesStr4File         文件名称(不包含路径或包名)匹配规则(正则表达式)
     * @param parentClass             父类class
     * @param parentClassIsInstanceOf 父类class是否多重继承
     * @return List<Class>
     */
    public static List<Class> findClassFromJar(java.io.File fileJar, String matchesStr4Package, String matchesStr4File, Class parentClass, boolean parentClassIsInstanceOf) {
        try {
            List<Class> classes = new ArrayList<Class>();

            List<File> filesJarClasses = findFilesFromJar(fileJar, matchesStr4Package, "[^$]*\\.class");
            for (File file : filesJarClasses) {
                //若文件名匹配
                //1、制定了父类时，若父类匹配，添加到结果
                //2、未指定父类时，直接添加到结果
                if (file.getName().matches(matchesStr4File)) {
                    String fileClassName = file.getClassName();
                    Class fileClass = null;
                    try {
                        fileClass = Class.forName(fileClassName);
                    } catch (Exception e) {
                        System.err.println(e.getClass().getName() + "[" + fileClassName + "]:" + e.getMessage());
                    } catch (Error e) {
                        System.err.println(e.getClass().getName() + "[" + fileClassName + "]:" + e.getMessage());
                    }

                    //未找到class时跳过
                    if (EmptyUtil.isEmpty(fileClass)) {
                        continue;
                    }

                    //class未annotation或interface或enum时跳过
                    if (fileClass.isAnnotation() || fileClass.isInterface() || fileClass.isEnum()) {
                        continue;
                    }

                    if (EmptyUtil.isNotEmpty(parentClass)) {
                        if (parentClassIsInstanceOf) {
                            if (parentClass.isAssignableFrom(fileClass)) {
                                classes.add(fileClass);
                            }
                        } else {
                            if (parentClass.equals(fileClass.getSuperclass())) {
                                classes.add(fileClass);
                            }
                        }
                    } else {
                        classes.add(fileClass);
                    }
                }
            }

            return classes;
        } catch (Exception e) {
            e.printStackTrace();
            System.err.println(e.getClass().getName() + ":" + e.getMessage());
        } catch (Error e) {
            e.printStackTrace();
            System.err.println(e.getClass().getName() + ":" + e.getMessage());
        }

        return null;
    }

    /**
     * 查找指定jar文件中的class
     *
     * @param fileJar            jar文件File对象
     * @param matchesStr4Package 包名匹配规则(正则表达式)(只与jar文件有关)
     * @param matchesStr4File    文件名称(不包含路径或包名)匹配规则(正则表达式)
     * @param parentClass        父类class
     * @return List<Class>
     */
    public static List<Class> findClassFromJar(java.io.File fileJar, String matchesStr4Package, String matchesStr4File, Class parentClass) {
        return findClassFromJar(fileJar, matchesStr4Package, matchesStr4File, parentClass, true);
    }

    /**
     * 查找指定jar文件中的class
     *
     * @param fileJar         jar文件File对象
     * @param matchesStr4File 文件名称(不包含路径或包名)匹配规则(正则表达式)
     * @param parentClass     父类class
     * @return List<Class>
     */
    public static List<Class> findClassFromJar(java.io.File fileJar, String matchesStr4File, Class parentClass) {
        return findClassFromJar(fileJar, ".*", matchesStr4File, parentClass, true);
    }

    /**
     * 查找指定jar文件中的class
     *
     * @param fileJar     jar文件File对象
     * @param parentClass 父类class
     * @return List<Class>
     */
    public static List<Class> findClassFromJar(java.io.File fileJar, Class parentClass) {
        return findClassFromJar(fileJar, ".*", ".*", parentClass, true);
    }

    /**
     * 查找指定jar文件中的class
     *
     * @param fileJar jar文件File对象
     * @return List<Class>
     */
    public static List<Class> findClassFromJar(java.io.File fileJar) {
        return findClassFromJar(fileJar, ".*", ".*", null, true);
    }

    /**
     * (vip)查找指定jar文件中的class
     *
     * @param filesJar                jar文件File对象list列表
     * @param matchesStr4Package      包名匹配规则(正则表达式)(只与jar文件有关)
     * @param matchesStr4File         文件名称(不包含路径或包名)匹配规则(正则表达式)
     * @param parentClass             父类class
     * @param parentClassIsInstanceOf 父类class是否多重继承
     * @return List<Class>
     */
    public static List<Class> findClassFromJars(List<java.io.File> filesJar, String matchesStr4Package, String matchesStr4File, Class parentClass, boolean parentClassIsInstanceOf) {
        try {
            List<Class> classes = new ArrayList<Class>();
            for (java.io.File fileJar : filesJar) {
                classes.addAll(findClassFromJar(fileJar, matchesStr4Package, matchesStr4File, parentClass, parentClassIsInstanceOf));
            }
            return classes;
        } catch (Exception e) {
            e.printStackTrace();
            System.err.println(e.getClass().getName() + ":" + e.getMessage());
        } catch (Error e) {
            e.printStackTrace();
            System.err.println(e.getClass().getName() + ":" + e.getMessage());
        }

        return null;
    }

    /**
     * 查找指定jar文件中的class
     *
     * @param filesJar           jar文件File对象list列表
     * @param matchesStr4Package 包名匹配规则(正则表达式)(只与jar文件有关)
     * @param matchesStr4File    文件名称(不包含路径或包名)匹配规则(正则表达式)
     * @param parentClass        父类class
     * @return List<Class>
     */
    public static List<Class> findClassFromJars(List<java.io.File> filesJar, String matchesStr4Package, String matchesStr4File, Class parentClass) {
        return findClassFromJars(filesJar, matchesStr4Package, matchesStr4File, parentClass, true);
    }

    /**
     * 查找指定jar文件中的class
     *
     * @param filesJar        jar文件File对象list列表
     * @param matchesStr4File 文件名称(不包含路径或包名)匹配规则(正则表达式)
     * @param parentClass     父类class
     * @return List<Class>
     */
    public static List<Class> findClassFromJars(List<java.io.File> filesJar, String matchesStr4File, Class parentClass) {
        return findClassFromJars(filesJar, ".*", matchesStr4File, parentClass, true);
    }

    /**
     * 查找指定jar文件中的class
     *
     * @param filesJar    jar文件File对象list列表
     * @param parentClass 父类class
     * @return List<Class>
     */
    public static List<Class> findClassFromJars(List<java.io.File> filesJar, Class parentClass) {
        return findClassFromJars(filesJar, ".*", ".*", parentClass, true);
    }

    /**
     * 查找指定jar文件中的class
     *
     * @param filesJar jar文件File对象list列表
     * @return List<Class>
     */
    public static List<Class> findClassFromJars(List<java.io.File> filesJar) {
        return findClassFromJars(filesJar, ".*", ".*", null, true);
    }

    /**
     * 根据class文件的File对象获取该class类名
     *
     * @param classFile class文件的File对象
     * @param pre       前缀
     * @return class类名
     */
    private static String file2ClassName(java.io.File classFile, String pre) {
        String classFilePathStr = classFile.getPath().replaceAll("\\\\", "/");
        pre = pre.trim().replaceAll("\\\\", "/");
        String className;
        className = classFilePathStr.substring(classFilePathStr.indexOf(pre) + pre.length(), classFilePathStr.indexOf(".class"));
        if (className.startsWith("/")) {
            className = className.substring(className.indexOf("/") + 1);
        }
        return className.replaceAll("/", ".");
    }
}